( function () {
const _changeEvent = {
  type: 'change'
};

class FlyControls extends THREE.EventDispatcher {
  constructor(object, domElement) {
    super();

    if (domElement === undefined) {
      console.warn('THREE.FlyControls: The second parameter "domElement" is now mandatory.');
      domElement = document;
    }

    this.object = object;
    this.domElement = domElement; // API

    this.movementSpeed = 1.0;
    this.rollSpeed = 0.005;
    this.dragToLook = false;
    this.autoForward = false; // disable default target object behavior
    // internals

    const scope = this;
    const EPS = 0.000001;
    const lastQuaternion = new THREE.Quaternion();
    const lastPosition = new THREE.Vector3();
    this.tmpQuaternion = new THREE.Quaternion();
    this.mouseStatus = 0;
    this.moveState = {
      up: 0,
      down: 0,
      left: 0,
      right: 0,
      forward: 0,
      back: 0,
      pitchUp: 0,
      pitchDown: 0,
      yawLeft: 0,
      yawRight: 0,
      rollLeft: 0,
      rollRight: 0
    };
    this.moveVector = new THREE.Vector3(0, 0, 0);
    this.rotationVector = new THREE.Vector3(0, 0, 0);

    this.keydown = function (event) {
      if (event.altKey) {
        return;
      }

      switch (event.code) {
        case 'ShiftLeft':
        case 'ShiftRight':
          this.movementSpeedMultiplier = .1;
          break;

        case 'KeyW':
          this.moveState.forward = 1;
          break;

        case 'KeyS':
          this.moveState.back = 1;
          break;

        case 'KeyA':
          this.moveState.left = 1;
          break;

        case 'KeyD':
          this.moveState.right = 1;
          break;

        case 'KeyR':
          this.moveState.up = 1;
          break;

        case 'KeyF':
          this.moveState.down = 1;
          break;

        case 'ArrowUp':
          this.moveState.pitchUp = 1;
          break;

        case 'ArrowDown':
          this.moveState.pitchDown = 1;
          break;

        case 'ArrowLeft':
          this.moveState.yawLeft = 1;
          break;

        case 'ArrowRight':
          this.moveState.yawRight = 1;
          break;

        case 'KeyQ':
          this.moveState.rollLeft = 1;
          break;

        case 'KeyE':
          this.moveState.rollRight = 1;
          break;
      }

      this.updateMovementVector();
      this.updateRotationVector();
    };

    this.keyup = function (event) {
      switch (event.code) {
        case 'ShiftLeft':
        case 'ShiftRight':
          this.movementSpeedMultiplier = 1;
          break;

        case 'KeyW':
          this.moveState.forward = 0;
          break;

        case 'KeyS':
          this.moveState.back = 0;
          break;

        case 'KeyA':
          this.moveState.left = 0;
          break;

        case 'KeyD':
          this.moveState.right = 0;
          break;

        case 'KeyR':
          this.moveState.up = 0;
          break;

        case 'KeyF':
          this.moveState.down = 0;
          break;

        case 'ArrowUp':
          this.moveState.pitchUp = 0;
          break;

        case 'ArrowDown':
          this.moveState.pitchDown = 0;
          break;

        case 'ArrowLeft':
          this.moveState.yawLeft = 0;
          break;

        case 'ArrowRight':
          this.moveState.yawRight = 0;
          break;

        case 'KeyQ':
          this.moveState.rollLeft = 0;
          break;

        case 'KeyE':
          this.moveState.rollRight = 0;
          break;
      }

      this.updateMovementVector();
      this.updateRotationVector();
    };

    this.mousedown = function (event) {
      if (this.dragToLook) {
        this.mouseStatus++;
      } else {
        switch (event.button) {
          case 0:
            this.moveState.forward = 1;
            break;

          case 2:
            this.moveState.back = 1;
            break;
        }

        this.updateMovementVector();
      }
    };

    this.mousemove = function (event) {
      if (!this.dragToLook || this.mouseStatus > 0) {
        const container = this.getContainerDimensions();
        const halfWidth = container.size[0] / 2;
        const halfHeight = container.size[1] / 2;
        this.moveState.yawLeft = -(event.pageX - container.offset[0] - halfWidth) / halfWidth;
        this.moveState.pitchDown = (event.pageY - container.offset[1] - halfHeight) / halfHeight;
        this.updateRotationVector();
      }
    };

    this.mouseup = function (event) {
      if (this.dragToLook) {
        this.mouseStatus--;
        this.moveState.yawLeft = this.moveState.pitchDown = 0;
      } else {
        switch (event.button) {
          case 0:
            this.moveState.forward = 0;
            break;

          case 2:
            this.moveState.back = 0;
            break;
        }

        this.updateMovementVector();
      }

      this.updateRotationVector();
    };

    this.update = function (delta) {
      const moveMult = delta * scope.movementSpeed;
      const rotMult = delta * scope.rollSpeed;
      scope.object.translateX(scope.moveVector.x * moveMult);
      scope.object.translateY(scope.moveVector.y * moveMult);
      scope.object.translateZ(scope.moveVector.z * moveMult);
      scope.tmpQuaternion.set(scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1).normalize();
      scope.object.quaternion.multiply(scope.tmpQuaternion);

      if (lastPosition.distanceToSquared(scope.object.position) > EPS || 8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {
        scope.dispatchEvent(_changeEvent);
        lastQuaternion.copy(scope.object.quaternion);
        lastPosition.copy(scope.object.position);
      }
    };

    this.updateMovementVector = function () {
      const forward = this.moveState.forward || this.autoForward && !this.moveState.back ? 1 : 0;
      this.moveVector.x = -this.moveState.left + this.moveState.right;
      this.moveVector.y = -this.moveState.down + this.moveState.up;
      this.moveVector.z = -forward + this.moveState.back; //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
    };

    this.updateRotationVector = function () {
      this.rotationVector.x = -this.moveState.pitchDown + this.moveState.pitchUp;
      this.rotationVector.y = -this.moveState.yawRight + this.moveState.yawLeft;
      this.rotationVector.z = -this.moveState.rollRight + this.moveState.rollLeft; //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
    };

    this.getContainerDimensions = function () {
      if (this.domElement != document) {
        return {
          size: [this.domElement.offsetWidth, this.domElement.offsetHeight],
          offset: [this.domElement.offsetLeft, this.domElement.offsetTop]
        };
      } else {
        return {
          size: [window.innerWidth, window.innerHeight],
          offset: [0, 0]
        };
      }
    };

    this.dispose = function () {
      this.domElement.removeEventListener('contextmenu', contextmenu);
      this.domElement.removeEventListener('mousedown', _mousedown);
      this.domElement.removeEventListener('mousemove', _mousemove);
      this.domElement.removeEventListener('mouseup', _mouseup);
      window.removeEventListener('keydown', _keydown);
      window.removeEventListener('keyup', _keyup);
    };

    const _mousemove = this.mousemove.bind(this);

    const _mousedown = this.mousedown.bind(this);

    const _mouseup = this.mouseup.bind(this);

    const _keydown = this.keydown.bind(this);

    const _keyup = this.keyup.bind(this);

    this.domElement.addEventListener('contextmenu', contextmenu);
    this.domElement.addEventListener('mousemove', _mousemove);
    this.domElement.addEventListener('mousedown', _mousedown);
    this.domElement.addEventListener('mouseup', _mouseup);
    window.addEventListener('keydown', _keydown);
    window.addEventListener('keyup', _keyup);
    this.updateMovementVector();
    this.updateRotationVector();
  }

}

function contextmenu(event) {
  event.preventDefault();
}

THREE.FlyControls = FlyControls;
} )();
